Welcome back.
In this lecture we're going to be taking a look at something in the Lua language called meta tables.
This is going to be somewhat more of an advanced topic.
So do not beat yourself up if you aren't able to understand it your first time around.
I didn't understand meta tables when I first got introduced to them as well, so if you didn't understand
the content in this lecture, feel free to continue moving through the course and come back to this
lecture at a later time.
So meta tables, what are they?
Are they a different data type in Lua?
Well, they aren't a different data type.
And in fact meta tables are literally just tables in Lua.
Except these tables have a special job.
Now we already know what a regular table is.
I'll just make a variable called my table here real quick, and we know we can use tables as arrays
or dictionaries so they can store index value pairs or key value pairs.
So here I'll have an array with some numbers inside of it.
And then to create a meta table we need to first create another table.
I'm just going to call this my meta table and set equal to an empty table.
And right now this is just a boring regular old table.
This doesn't become a meta table until we attach this table to another table, like my table, using
a function in Lua called set meta table.
Set meta table is a global function, so we can just type out the name.
And as you can see it pops up right here.
And it wants to take in a table.
And then it needs to take in another table.
That's going to act as the meta table.
And the purpose of this function it says sets the given table's meta table.
The purpose of a meta table is to act as a helper table, to define what the behavior of another table
should be when certain events occur.
A good analogy for a meta table would be a restaurant menu.
Imagine you're looking at the selection of food in the menu, and you want to make a modification to
some kind of menu item.
Maybe you want to add more meat, or maybe you want to add extra spice.
Whatever the case may be, that's a specific event occurring where you want to modify something already
predefined in the menu.
Now let's say there's a small section in the menu that details exactly what happens when you make a
modification to a menu item, like if you wanted to add extra meat, that's going to be $4 extra, or
if you wanted to add extra spice, that's going to be a buck 50 extra.
That small section in the menu that tells you what exactly will happen when you try to modify the menu
is like a meta table in Lua.
So let me set this meta table on my table here.
We need to pass my table first, and then we can pass my meta table and boom!
Now right after line five, my meta table is officially a meta table because it is attached to my table
fulfilling the purpose or the role of a meta table.
Going back to our menu example, let's say we wanted to be able to add a number into our table.
So let's say we created a new variable.
We call it other table and we want it to be equal to my table plus the value of three.
By default.
The Lua interpreter has no idea how to do this.
It doesn't know how to add this number into the table using this addition operator.
And this same thing could happen with a menu that has no section inside of it.
Defining what should happen when, for example, someone requests extra meat, how much extra money
should the restaurant charge for that customer?
Now, wouldn't it be neat if we were able to define what exactly we want to happen when we, for example,
try to add a number into our table and then this is where we can use our meta table for that task.
The purpose of the meta table is to store functions that get executed internally by the Lua interpreter
when specific events occur, like when we try to add a number to our table.
The creators of Lua defined a set of identifiers or keys that we must use inside of the meta table,
and we can associate a function with those keys and those key value pairs that get executed during a
certain situation are the meta methods, and the term or phrase method is just another word for function,
but methods usually indicate that the function belongs to something.
In this case, if we create any meta methods or functions for our meta table, those functions belong
to the meta table, so technically they are a method.
Not all meta methods have to be functions, but most of the time they will be functions.
If we take a look at the robotics documentation.
As you can see, there is a huge list of all of these different predefined meta methods that exist that
we can use for meta tables.
For example, we can define what we want to happen when we use the addition operator on our table.
And that's defined by the special key right here called underscore underscore add.
There's also a meta method for when we want to use the subtraction operator multiplication division.
If you use modulus the exponentiation.
Even two string and a whole bunch more in here.
And all of these different meta methods will get executed or use when these specific events happen,
like when we use the addition operator or whatever.
So let's go ahead and use the underscore underscore add meta method to solve our issue of trying to
add that number of three into our table.
And as you can see right here, it says that it's going to be passed a table.
And the value that we're attempting to add into the table.
So this is going to have to be a function.
So back in studio we can go into our meta table or the table acting as our meta table.
And we can create a new key inside of it.
And we need to call it underscore underscore add.
The reason we have to do this is because these are just the names that the creators of Lua have defined
for meta methods.
And then we can set this key equal to a new function.
And like we saw in the documentation, it's going to be passed the table.
So we could call this our table.
And then the value that we're attempting to add into our table.
So we'll just call it value.
Now inside of this function is where we have the ability to define exactly what we want to happen when
we try to add, for example, the number three into our table.
So what I'm going to do is I'm just going to create a simple for loop that's going to loop through every
index value pair inside of our table.
And what we want to do is we just want to go ahead and update inside of our table at the current index,
we want to set it equal to its current value plus the value we're attempting to add into the table.
And then this method expects us to return a value back out of the function.
So that way, once this addition operation down here completes, we can store the result of this operation
inside of our variable right here called other table.
So let's go ahead and just return our table back to where this function is called which is down here.
So let's print other table into the console.
And then let's run all of this code inside of the command line.
And let's see what output we get.
All right.
It printed a table.
And as you can see it has printed our original table which contained the values of one, two and three.
But now it has added the value of three to every single index value pair.
So one became four, two became five, and three became six.
Very cool.
Now something very important to note is that most of the time you will not want to directly alter the
table passed to our meta method.
Instead, what we want to do is we want to create a new table, and we want this table to observe the
changes without affecting the original table passed to the meta method.
Why do we want to do this?
Well, it's because we don't want our function to have any what we call side effects.
So what I'm going to do here is I'm actually going to print other table, but then I also want to print
my table.
Afterwards.
We're going to print both of these tables out into the console.
And if we run the code and we take a look at both tables, what you're going to notice is that hold
on.
Both tables are exactly the same.
They both have the values of four, five and six.
Even my table had the value of three added to it, and both of them contain four, five and six.
What is happening?
Well, the issue that's happening is that because we are performing these alterations to the table directly
on the table, passed to the meta method, we are inadvertently manipulating the table stored in my
table, and then we're storing that exact same table in the other table.
So what this means is that other table and my table both refer to the exact same table in memory.
It's the same thing.
If I manipulated other table, it would also affect my table.
And if I were to manipulate my table it would also affect other table.
So let's go ahead and manipulate my table by using some kind of function in the table library.
Like for example, let's clear out all of the index value pairs inside of my table.
Do you think it's also going to affect the other table or the table stored in other table?
Well, let's find out.
Let's print both of these tables again in the console.
And then let's run this code.
And what do you notice.
Well both of our tables were filled with index value pairs.
But now both tables have been cleared out.
And that's because as I stated, both of these variables are referring to the exact same table in memory.
So directly manipulating a table passed to our function is a big no no, and it can cause a lot of confusion
and side effects in your code.
So what we want to do is we want to basically promise that we are not going to modify or manipulate
our table, and we're only going to use this as basically a read only table.
And instead we want to create a new table in here and return that new table.
This way.
We're kind of closely upholding a concept in functional programming called pure functions.
And basically a pure function should take an input like we're taking an input here and it should give
an output.
And if the input is the same, the output should also be the same.
The function should not cause any side effects.
So we should not be manipulating the values passed to our function, and the function should also not
access any data outside of itself.
The only data are pure functions should be able to access is whatever values are passed as arguments
to our functions.
So let's go ahead and change the code inside of our add meta method.
And instead what we're going to do is we're going to create a brand new table.
Let's just call it result.
There's our new table.
And instead of manipulating our table and the values inside of our table, let's go ahead and swap this
out with our new result table.
So we're going to be storing still at the same index, the value that is inside of our table plus the
new value we want to add.
And then we can go ahead and return this new result table.
So let's go ahead and copy all of this code again and run this brand new code.
And let's see what outputs we get.
Well let's take a look.
If you noticed that issue or that side effect that our add meta method was causing in our code has now
disappeared.
So now we have two unique tables in different places in memory.
We have the new table that had the value of three added to all the index value pairs.
And we still also have our original table with the values of one, two and three.
And even after we cleared all of the values inside of my table, all of the index value pairs in the
other table still exist, which is great.
Back in the documentation, let's go ahead and look at another meta method and see if we can implement
that inside of our meta table.
In fact, let's go ahead and do the subtraction operator.
So as a challenge for you, I would like you to implement the subtraction meta method inside of your
meta table.
Take a few minutes to do that, and then I will go ahead and show you my solution step by step afterwards.
So pause the lecture now and I'll see you in a little bit okay.
So let's go ahead and take a look at how we can add functionality for the subtraction meta method.
It's actually quite simple because we basically already have the logic written out for our subtraction
meta method right here.
The only thing we need to do is just replace this addition operator with the minus operator.
So let's just copy this function here, and then let's create a new key which is going to be underscore
underscore sub for subtraction.
And we're going to set it equal to the same function.
But instead let's swap this addition operator for a subtraction operator.
Now we should be able to perform subtraction on our table.
So let's go ahead and swap this from an addition to a minus symbol.
And let's run this new code and see if the subtraction function worked.
So if we execute the code let's go ahead and take a look at the output.
And look at that.
We have our new table that had the value of three subtracted from every single index value pair.
So one became negative two, two became negative one and three became zero.
Very cool.
Another thing I would like to highlight that's important.
For you to know.
So that way you don't make the same mistake.
Is that after we have returned this new result table from our meta methods, you're going to notice
that this new result table doesn't have a meta table attached to it.
So that means, for example, the table that is stored in other table is no longer able to have any
of these other operations performed on it, like addition and subtraction, because it's missing that
meta table.
We didn't attach it in any of these functions here.
So to demonstrate this, if I were to set other table equal to itself, and let's say we added the value
of five to it and we tried to execute this code, you're going to see that we've run into an error.
And it says attempt to perform arithmetic addition on table and number.
And this is the interpreter telling you or it's screaming at you saying, hey, I don't know how to
do this.
And that's because we lost the meta table.
So to fix this issue, we just need to simply reattach the meta table back on to our new result table.
So an easy way for us to do that is to just call set meta table, pass our result table here, and then
pass our meta table which is my meta table.
And uh oh what's going on here?
Unknown global.
My meta table.
Well, the issue is right now is because my meta table is a local variable, we are not able to access
or refer to my meta table within the table being assigned to my meta table.
So to fix this, we can just simply declare my meta table up here and initialize it with no value.
And then afterwards we can go ahead and assign the value to the already existing my meta table variable.
And as you can see now that has fixed the issue because the interpreter now knows what variable we're
referring to, and we can go ahead and do this same operation in our addition meta method.
So let's go ahead and paste that inside of there as well.
And now if we run this new code.
You can see that our error has disappeared because the new table stored inside of other table has our
meta table attached to it, and it knows how to do the addition operation.
Another commonly used meta method is called underscore underscore index, and this method is used when
you attempt to index or access a key in your table that does not exist.
If it does not exist, then what the lure interpreter is going to do is it's going to check to see if
your table has a meta table, and if that meta table has this meta method.
If it does have this meta method, then the interpreter will use it to get a potential value for the
key that we're trying to index for in the table.
As you can see, the description says it is fired when table when you try to index with something is
indexed.
If that index is nil, it says it can also be set to a table, in which case that table will be indexed
instead.
So you can set the underscore underscore index meta method to be either a function or you can also set
it to be a table.
So back inside of studio let's say we tried to print whatever value is stored in my table at the index
of four.
Now obviously we only have three elements in the table.
So this should print nil into the console.
So if we execute the code as you can see it printed nil.
But let's say we wanted to be able to maybe have some kind of table store, a bunch of key value pairs
that could be shared across multiple different tables.
So it can kind of act as like, I don't know, a table full of constants or something.
This is where we can use the underscore underscore index meta method.
And first to demonstrate how it works, I'm just going to set it equal to a function.
And this function is going to be passed the key that we're trying to index into our table.
So it's going to be first past our table.
So I'll call it our table.
And then the key that we're attempting to index in our table.
And let's just go ahead and print out that key into the console.
So now what should happen here is we should also get uh, four printed out in the console as well as
nil.
So let's run this new code.
And as you can see it printed four, because it printed our key that we tried to access our table with.
And then it also printed nil because our function didn't return anything, I could return some kind
of constant value here.
Like if I return five.
What's going to happen now is that if I were to run this code, you're going to see that instead of
printing nil, it now prints five, because that's the new value that we're saying, hey, use this
value when you try to index your table with an index that does not exist.
And as the documentation stated, we could also set underscore underscore index to be a table.
So what I'm going to do real quick here is I'm just going to get rid of that line.
And then let's try to index my table with a string of underscore underscore let's say add.
And if we run this code you're going to see that obviously it printed nil because we do not have access
to this key.
If we think about our table here right there is no key value pair inside of the table called underscore
underscore add.
However, what if we wanted to be able to access this meta method that's stored in the meta table attached
to my table?
Well, what we can do is we can set underscore underscore index to our meta table.
So my meta table.
And now what's going to happen is that anytime we try to index my table with a key that does not exist,
it's now going to search through this table instead and see if it can find that key.
And of course we have the underscore underscore add key right here.
So now what should happen is we should have this print out a function into the console instead.
And unfortunately actually that's still printed nil.
So let me try something a little bit different here.
It might be because I'm declaring it right inside of the table.
So let me actually delete that and move it outside of my meta table.
So we could do my meta table dot underscore underscore index and set it equal to itself.
And let's try to see if it'll work that way.
So we'll run the code okay.
Perfect.
Yeah it works this way.
As you can see our function has printed out inside of the console why it didn't work the first time.
I think it might have to do to the fact that it might be some kind of, like, endless cycle, because,
I mean, if you kind of think about it, if you're setting underscore, underscore, index or a value
in here equal to my meta table, which is also the value we're trying to set to the variable itself,
it kind of goes in a loop.
So I think that's why it wasn't working.
But as you can see we declared it outside.
After we initialized my meta table with this table.
We just added this new underscore underscore index key to my meta table.
Set it equal to itself.
And then now we were able to access the functions inside of the meta table.
And then we could also define other keys in our meta table.
Like we could say hello.
And set it equal to a value of five, and we should be able to access that key.
So if I do dot.
Hello.
As you can see it's actually also auto filling for us two.
So the linter in Lua or basically the intellisense that helps you auto fill your code.
It knows that my table has my meta table attached to it, and it also knows that my meta table has an
underscore underscore index meta method.
And that meta method is set to a table that contains our key, which is why we are able to auto fill
right here.
And if we run this code, as you can see, it's going to print out the value of five, which is very
cool.
So the link to all of these meta methods will be attached to this lecture.
And what I would like for you to do is I would like for you to go to that link, take a look at all
these meta methods and pull out a couple that you want to experiment with.
Have fun with it.
Try to see how they work and see how you can use them in your meta table.
Pause the video.
Now take a few minutes of your time to mess around with these different meta methods, because experimentation
and practicing messing around with them is the best and fastest way you're going to be able to learn
and pick up this new concept.
So yeah, meta tables, they are not as scary as some people make them out to be.
They're simply helper tables that define the behavior for other tables when certain situations occur.
Once more, if you had a hard time understanding the information in this lecture, feel free to attempt
the Meta Tables quiz and then just move on through the course.
You can always come back to this lecture at a later date.
In the next lecture, we're going to continue expanding upon meta tables by using them to imitate a
paradigm in programming called object oriented programming.
This is somewhat of another advanced topic, so don't feel discouraged if you don't understand it at
first.
With all of that said, I'll see you in the next lecture.